from __future__ import with_statement
import sys
import os, os.path
import urllib2
from orpg.external.etree.ElementTree import ElementTree, Element

from orpg.dirpath import dir_struct
from orpg.tools.settings import settings

class OpenRPGUpdater(object):
    is_update = False
    is_major = False
    update_done = False
    current_release = ElementTree()
    remote_packages = ElementTree()
    remote_release = ElementTree()
    remove_files = ElementTree()
    auto_update = False
    fast_update = False
    updater_url = "http://openrpg.sourceforge.net/"
    c_section = 'beta' #TODO: Change to release
    c_version = ''
    c_revision = ''

    def check_updates(self):
        self._get_settings()
        new_version = self._check_updates()

        if not self.is_update or (not self.auto_update and \
                                  not self.fast_update) or \
           new_version is None:
            return

        # By getting here there is an update and we are allowed to auto update
        # So Build the update tree
        self.build_update_tree(self.c_section,
                               new_version.get('version'))

        # Ok self.remote_release now only contains the files / directories we
        # need to update
        if not self.is_major and self.fast_update:
            self.do_updates()
        elif self.auto_update:
            self.do_updates()

    def do_updates(self, yield_func=None):
        """
        This goes though the now modified self.remote_release and creates all
        the listed directories, then downloads all the files to the correct
        locations, overwritting the current ones. It ignores files set to ignore
        """

        base_dir = os.path.abspath(os.path.basename(__file__) + '/../')
        for r_el in self.remote_release.getiterator('dir'):
            new_dir = base_dir + os.sep + r_el.get('path')
            try:
                os.makedirs(new_dir)
            except:
                pass

        ignore = set()
        if os.access(dir_struct.get('user') + 'updater.ignore', os.R_OK):
            with open(dir_struct.get('user') + 'updater.ignore', "r") as f:
                for line in f.read().replace('\r','').split('\n'):
                    ignore.add(line.strip())

        for r_el in self.remove_files.getiterator('remove'):
            file_path = r_el.get('path')
            if os.access(file_path, os.F_OK) and file_path not in ignore:
                try:
                    os.remove(file_path)
                    self.write(r_el.get('path') + " - REMOVED")
                except IOError:
                    continue

        for r_el in self.remote_release.getiterator('file'):
            old_file = base_dir + os.sep + r_el.get('path') + os.sep + r_el.get('name')
            if os.access(old_file, os.W_OK) or not os.access(old_file, os.F_OK):
                file_path = (r_el.get('path') + '/' + r_el.get('name')).strip('/')
                if file_path in ignore:
                    self.write(file_path + " - IGNORED")
                else:
                    with open(old_file, "wb") as f:
                        f.write(urllib2.urlopen(r_el.get('url')).read())
                        self.write(file_path + " - Updated")
                if yield_func:
                    yield_func()

        self.update_done = True

    def build_update_tree(self, section, version):
        try:
            url = self.updater_url.strip('/') + '/' + section + '/' + version + '/release.xml'
            conn = urllib2.urlopen(url)
            self.remote_release.parse(conn)
        except urllib2.HTTPError:
            self.write("The update server seems to be down, try again later\n")
            return
        finally:
            try:
                conn.close()
            except UnboundLocalError:
                pass

        try:
            url = self.updater_url.strip('/') + '/' + section + '/' + version + '/remove.xml'
            conn = urllib2.urlopen(url)
            self.remove_files.parse(conn)
        except urllib2.HTTPError:
            self.write("The update server seems to be down, try again later\n")
            return
        finally:
            try:
                conn.close()
            except UnboundLocalError:
                pass

        if self.current_release.getroot() is not None:
            for lo_el in self.current_release.getiterator('dir'):
                for r_el in self.remote_release.getiterator('dir'):
                    if r_el.get('path') == lo_el.get('path'):
                        self.remote_release.getroot().remove(r_el)

            for lo_el in self.current_release.getiterator('file'):
                for r_el in self.remote_release.getiterator('file'):
                    if r_el.get('checksum') == lo_el.get('checksum'):
                        self.remote_release.getroot().remove(r_el)

    """
    These should be over written by the actual ui for the updater
    """
    def prompt(msg, choices=None, default='0'): pass
    def write(msg): pass

    """
    Private methods that shoudnt be called outside of this file
    """
    def _get_settings(self):
        self.auto_update = settings.get('auto_update', "Off").lower() in ('on','true')
        self.fast_update = settings.get('fast_update', "Off").lower() in ('on','true')
        self.updater_url = settings.get('release_url')

    def _check_updates(self):
        """
        Fetches the packages.xml from the remote server and does a quick check
        to see if there are updates you need. The process goes like this;
        1) grab the users current section from their local release.xml
        2) grab all packages in that section from the packages.xml
        3) generate a reverse sorted list of all versions in that package
        4) compare the first element of the list to the users current version
            a) If they are diffrent there is an update, stop process
        5) find their package in the the packages list and compare the revisions
            a) If they are diffrent there is a minor update, stop process
        6) Return false, no updates
        """

        try:
            url = self.updater_url.strip('/') + '/' + 'packages.xml'
            conn = urllib2.urlopen(url)
            self.remote_packages.parse(conn)
        except urllib2.HTTPError:
            self.write("The update server seems to be down, try again later")
            return None
        finally:
            try:
                conn.close()
            except UnboundLocalError:
                pass

        try:
            self.current_release.parse(dir_struct.get('home') + 'release.xml')
            self.c_section = self.current_release.getroot().get('section', 'release')
            self.c_version = self.current_release.getroot().get('version')
            self.c_revision = self.current_release.getroot().get('revision')
        except IOError:
            self.is_update = True
            self.is_major = True

        packages = self.remote_packages.find(self.c_section).getiterator('package')

        user_pkg = None
        versions = {}
        for pkg in packages:
            versions[pkg.get('version')] = pkg
            if pkg.get('version') == self.c_version:
                user_pkg = pkg

        vk = versions.keys()
        vk.sort()
        vk.reverse()

        if user_pkg is None or vk[0] != self.current_release.getroot().get('version'):
            self.is_update = True
            self.is_major = True

        if user_pkg is not None and user_pkg.get('revision') != self.c_revision:
            self.is_update = True

        if self.is_major:
            return versions[vk[0]]
        else:
            return user_pkg